<?php

/** Template library to import and parse HTML (and in the future other, like
 *  XML) files. The purpose is that a data object can be described while
 *  the designing the form. The HTML - or other formatted - file can contain
 *  all information needed to create required tables or access them.
 *  @author Henri Ranki & Jyry Kuukkanen
 *
 *  $Id: dztemplate.php 597 2006-04-03 16:15:46Z jyry $
 */


include_once("soset.php");
include_once("sostring.php");
include_once("sogeneric.php");
include_once("dzinifiles.php");

include_once("dzconst.php");
include_once("dzstruct.php");
include_once("dzsrcentity.php");
include_once("dzfeaturespool.php");
include_once("dzsubari.php");
include_once("dzpagelinks.php");
include_once("dzdbgeneric.php");


/** Import types */
define(DZT_TYPES, "html");
define(DZT_TYPE_HTML, "html");

/** Import types file extensions*/
define(DZT_FILE_EXT_HTML, ".".DZT_TYPE_HTML);
define(DZ_INI_EXT, ".ini");

/* File extensions */
define(DZS_CONF_EXT, ".dsi");
define(DZS_CACHE_EXT, ".dsc");
define(DZS_ENTITIES_EXT, ".ent");
define(DZS_HTML_EXT, ".html");
define(DZS_LANG_EXT, ".txt");


/** Define init actions. These are actions that are performed first after the
    form is loaded at user end. Output filter uses init action data found in
    ScriptInfo class to assign init action to form it creates. When defining
    init actions with addInitAction method you can pass a parameter for the
    init action. See the method definition later in this file. */
define(DZIA_SET_FOCUS, 1); /** When the form is first loaded the focus is moved
                               to the widget which name is given as parameter.*/
define(DZIA_SET_LAST_FILTER, 2); /** Set the information on row that last time
                            launched the filter change even. This is to prevent
                            filter change to occur again when returning
                            focusing the same row again. */
define(DZIA_SET_ERROR_FLAG, 3); /** Set the flag that an error occurred. So the
                                focus if sticked to current field */
define(DZIA_TO_NEXT_COMPONENT, 4); /** Set the focus to next component */
define(DZIA_OPEN_NEW_WINDOW, 5); /** Opens a new browser window with aadress
                                     given as a parameter.*/
define(DZIA_UPDATE_CALC_FIELDS, 6); /** Update the calculate fields when
                                        the form is loaded.*/
define(DZIA_SUSPEND_TIMER, 7); /** Set the suspend timer */

define(DZIA_INIT_DATE_BOX, 8); /** Initialize the date box */

/** The data structure that is used to return the result to Jarva.
 *  @var int $Status The dzManager result
 *  @var string $Screen The next view to show user
 *  @var $NextState The state that will start the process when entering to
 *      the state machine next time.
 *  @var $Serialize Serialize, delete serialized session or don't serialize.
 */
class dzAsReqResult {
    var $Status;
    var $Screen;
    var $NextState;
    var $Serialize;
} // class dzAsReqResult


/** List column format definition class.
 *  @var int $Width Width.
 *  @var string $Title Title
 *  @var string $Align Alignment "left", "right"...
 */
class dzListCol {
    var $Width;
    var $Title;
    var $Align;
}; //  dzListCol


/** Form options class.
 *  Class to import FormOptionsIni data and store them to properties.
 *  @var soSet/dzListCol $ListCols List column defs conditions
 *  @var soSet/string $StongaButtons Manually defined button lists for
 *      stonga in every mode.
 *  @var soSet/any $Other Other miscellaneous settings
 *  @var array/soSet $Events Events
 *  @var ? $Validate Form validation information - not used ??? XXX Henkka
 */
class dzFormOptions {
    var $ListCols;
    var $StongaButtons;
    var $Other;
    var $Events;
    var $Validate;

    /** constructor
     */
    function dzFormOptions() {
        $this->ListCols = new soSet();
        $this->StongaButtons = new soSet();
        $this->Other = new soSet();
        $this->Events = array();
    } // dzFormOptions


    /** Parses the option information from the ini string
     *  The information is stored in this class
     *  @param string $IniStr Ini string process in order to set up properties.
     *  @param dzStruct $Struct Struct used to rename the aliases
     */
    function setIniStr($IniStr, &$Struct) {
        $ini = new soIni($IniStr);
        if ($keys = $ini->getValueKeys(DZ_EVENT_INISECTION)) {
            foreach ($keys as $key) {
                $this->Events[$key] =
                    $ini->getValue(DZ_EVENT_INISECTION, $key, NULL);
            }; // foreach
        }; // if

        $ini = new dzIniData($Struct, $IniStr);

        /* Parse all sections */
        $scount = $ini->getSectionCount();
        for ($c = 0; $c < $scount; $c++) {
            $name = $ini->getSectionName($c);
            switch ($name) {
                case DZ_LIST_INISECTION:
                    $cols = $ini->getSectionKeys($c);
                    $ccount = count($cols);
                    for ($i = 0; $i < $ccount; $i++) {
                        /* Get the parameters for one column */
                        $col_options = $ini->getValueSet($c, $cols[$i]);
                        /* Store the parameters in dzListCol class. This is
                          to speed up the creation of the list view later in
                          dzAsm*/
                        $col = new dzListCol();
                        $col->Align = $col_options->getItem("align");
                        $col->Width = $col_options->getItem("width");
                        $col->Title = $col_options->getItem("title");
                        /* Rename the alias and store the column */
                        $cols[$i] = $Struct->findCol($cols[$i]);
                        if (!$col->Title) {
                            $col->Title = soExtractVal($cols[$i], ".", 2);
                        }; // if
                        $this->ListCols->setItem($cols[$i], $col);
                    } // for
                    break;

                case DZ_STONGA_INISECTION;
                    /* Store stonga buttons for every mode */
                    $this->StongaButtons = $ini->getSection($c);
                    break;

                default:
                    /* Take all the key value pairs */
                    $other = $ini->getSection($c);
                    /* Join the information to the form options*/
                    $this->Other->JoinSet($other);
            } // switch
        } // for
    } // setIniStr

}; // dzFormOptions



/** Interfaces is how to treat db<->app values
 *  Sometimes values to be stored in database are not the same than those
 *  used by application. Interface is a way to sort out the issue.
 *  An interface contains a list of db_value:client_value pair that means
 *  that when client_value is received from the client side, it is converted
 *  to db:value - and the other way round.
 *  Ini source may also contain instructions to get these value pairs from
 *  a database table. The format in the ini source is:
 *  ifacename=table_name,db_value_col:client_value_col,where_col:where_val
 *  where table_name is the table to fetch values from, db_value_col is the
 *  column name to get db values from, client_value_col is the column name
 *  to get client values from, where_col is the column name to limit values
 *  by the value in where_val. There can be more than one where_col:where_val
 *  pairs separated by plus (+) sign.
 *  @var soSet $__Interfaces A set that stores all interfaces. Each value
 *  in this set contains a array of db_value:client_value pairs, (colon
 *  delimited).
 *  @var soSet $__Interfaces Interfaces
 *  @package dztemplate
 */
class dzInterfaces {
    var $__Interfaces;

    // constructor
    function dzInterfaces() {
        $this->__Interfaces = new soSet();
    } // constructor


    /** Sets up interfaces based on ini stream.
     *  @var string $IniStr Ini stream (file/string)
     *  @return int No. of interfaces set up
     */
    function setup($IniStr) {
        $ini = new soIniData($IniStr);
        $i = 0;
        $value = $ini->getValue(DZ_INTERFACES_INISECTION, $i);
        while (!$ini->Error) {
            $key = $ini->getCurrentKeyName();
            if (substr($value, 0, 8) == "table://") {
                $def = soExtractVal($value, "table://", 2);
                list($table, $cols_str, $keys_str) = soExplode($def, ",");
                $cols = soExplode($cols_str, ":");
                $key_pairs = soExplode($keys_str, "+");
                $keys = soStrToAArray($key_pairs, 1, ":");
                $result = dzQuickFetch($table, $cols, $keys,
                                       array($cols[1] => 0));

                $value = "";
                if ($result) {
                    $rcount = $result->getRowCount();
                    $keys = $result->getKeys();
                    for ($r = 0; $r < $rcount; $r++) {
                        $value = soJoinStr($value, ",",
                            $result->getArrayItem($table.".".$cols[0], $r).":".
                            $result->getArrayItem($table.".".$cols[1], $r), 1);
                    }; // for
                }; // if $result
            }; // if
            $this->__Interfaces->setItem($key, soExplode($value, ","));
            soDebug("dzTemplate, dzInterfaces->setup: '$key' => '$value'",
                    SOD_DET);

            $value = $ini->getValue(DZ_INTERFACES_INISECTION, ++$i);
        }; // while

        return $i;
    } // setup


    /** Returns an interface as an soSet.
     *  @param string $Name Interface name.
     *  @return soSet Returns a set where each key contains the db_value and
     *      the value client_value.
     */
    function getInterface($Name) {
        $result = new soSet();
        $values = $this->__Interfaces->getItem($Name);
        if ($this->__Interfaces->Error) return $result; // <-- EXIT

        foreach ($values as $value) {
            $result->addItem(soExtractVal($value, ":", 1),
                             soExtractVal($value, ":", 2));
        }; // foreach

        return $result;
    } // getAsSet

}; // dzInterfaces

/** Define errors that may occur during initializing template. */
define(DZ_TMPL_PARSE_FAILED, 1);
define(DZ_TMPL_CACHE_WRITE_FAILED, 1);


/** Template class.
 *  Class to import, translate (language) and cache the source form file.
 *  Also is used as a base for producing client side output.
 *  @var dzScriptFeatureSpool $ScriptFeatureSpool Information used when
 *       creating scripts for the form. Information is inserted outside of the
 *       dzAsm class. dzAsm take and parses this structure when producing the
 *       scripts.
 *  @var soSet $ClientWidgetMap Maps the widget names in the code sent to
 *       client and the column names used in Dataxi internally. Field name on
 *       the form is the key. The value is n array with two items: the entity
 *       name and the row number. The external (client) field naming depends on
 *       the used form markup language. The naming for each language should be
 *       described separately in the implementation file of output filter for
 *       that markup language. These files are named as follows:
 *       dzout<type>.php where <type> is the type of the markup language like
 *       "html".
 *  @var dzSubari $Subari Structure to store the information on subari.
 *  @var dzSessionInfo $SessionInfo Basic session information
 *  @var dzAsReqResult $RequestResult Has all results from one request to asema
 *  @var soset/string $WidgetData Data from the widgets on the form. These was
 *       posted from the client
 *  @var int $PageNo Current page no.
 *  @var dzFormOptions $FormOptions Form options XXX private?
 *  @var dzLang $__Lang Object to handle translations
 *  @var dzInterfaces $__Interfaces db_col:app_col interfaces
 *  @var array/string $__Inis Main ini blocks: ext/in
 *  @var int $__Pseudo Flag for pseudo form - obsolete?
 *  @var dzSECache $__Cache Source entities to/from cache file
 *  @var dzStruct $__Struct Temp structure for dataxi
 *  @var soSet/any $__ViewData View related data see data type definitions in
 *      the beginning of the this file. Use get/setViewData methods to access
 *      data in this property
 */
class dzTemplate extends soBaseClass {
    var $ScriptFeatureSpool;
    var $ClientWidgetMap;
    var $Subari;
    var $SessionInfo;
    var $RequestResult;
    var $WidgetData;
    var $PageNo = 0;
    var $FormOptions;

    /* @access private */
    var $__Lang;
    var $__Interfaces;
    var $__Inis;
    var $__Cache;
    var $__Struct;
    var $__ViewData;

    /* XXX How the the sub view will be implemented in the new structure?
       Following two properties are old sub view stuff and might be replaced
       completely and are therefore not documented in proper manner */
    var $SVData;        /** soSet sub view data that was required. Values are
                         *  filled in here after returning from the sub view to
                         *  this view. Filling is done in
                         *  DZ_STATE_TO_PARENT_VIEW state */
    var $UseSVData;     /** array Information how the data from the sub view
                            should be used. */
    var $_Menu;         /** dzMenu Menu object provided through dzAhjo. */

    /** Constructor
     *  @param int $Type Type of the view XXX HENKKA CONSTANT !!!
     *  @param string $SrcType Type of the source XXX HENKKA CONSTANT !!!
     *  @param string $SrcStream Stream source to point to import file, addr
     *      etc.
     *  @param object dzSessionInfo $BasicSessionInfo Session owner and id
     *       information
     */
    function dzTemplate($Type, $SrcType, $SrcStream, $BasicSessionInfo) {
        soDebug("dzTemplate->constructor: viewtype='".$Type."', ".
                "src_type='".$SrcType.", ".
                "src_stream='".$SrcStream."'", SOD_LO);
        $this->SessionInfo = $BasicSessionInfo;

        /* Initialised to NULL to indicate nothing is read/processed yet */
        $this->__Cache = NULL;
        $this->_Menu = NULL;

        $this->__Lang = new dzLang();
        $this->WidgetData = new soSet();
        $this->__Cols = new soSet();
        $this->__Tables = new soSet();
        $this->FormOptions = new dzFormOptions();
        $this->__Interfaces = new dzInterfaces();
        $this->ScriptFeatureSpool = new dzScriptFeatureSpool();
        $this->__Inis = array();
        $this->__EntityPtr = NULL;

        /* Set the initial information for the view */
        $this->__ViewData = new soSet();
        $this->setViewData(DZ_VI_SOURCE, $SrcStream);
        $this->setViewData(DZ_VI_TYPE, $Type);
        $this->setViewData(DZ_VI_SOURCE_TYPE, $SrcType);
        $this->setViewData(DZ_VI_DISPOSABLE, TRUE);
        $this->setViewData(DZ_VI_SYS_VIEW, FALSE);
        $this->setViewData(DZ_VI_ENTER_STATE, 0);
        $this->setViewData(DZ_VI_MODE, DZM_NONE);

        /* If data form view create subari for this view and search for
         *  the help file  */
        if (($Type == DZ_VIEW_TYPE_DATAXI) ||
            ($Type == DZ_VIEW_TYPE_PSEUDO) ||
            ($Type == DZ_VIEW_TYPE_PICK_UP)) {
            $subari = new dzSubari($SrcStream, $this->SessionInfo->Lang);
            $this->Subari = $subari;
            $this->readForm();

            /* Search for help file for this form. */
            $help = $this->searchFormHelp($SrcStream);
            $this->setFormOption(DZFO_HELP, $help);
        } // if $type

        /* Create result data structure. Holds Asema produced result. */
        $this->RequestResult = new dzAsReqResult();
        /* Set the default values to result */
        $this->resetReqResult();
    } // dzTemplate


    /** Returns an interface as an soSet.
     *  This is a simple wrapper method to call its equivalent in Interfaces.
     *  @param string $Name Interface name.
     *  @return soSet Returns a set where each key contains the db_value and
     *      the value client_value.
     */
    function getInterface($Name) {
        return $this->__Interfaces->getinterface($Name);
    } // getAsSet

    /** Initialize the request result structure
     */
    function resetReqResult() {
        $this->RequestResult->Screen = "";
        $this->RequestResult->NextState = DZ_STATE_UNDEFINED;
        /* Following are set the most usual values to get the code
           slightly smaler */
        $this->RequestResult->Status = DZ_MANAGER_SEND_SCREEN;
        $this->RequestResult->Serialize = DZMNG_SER_YES;
    } // resetReqResult


    /** Returns page links available to the user.
     *  @return array/dzPageLink An array of dzPageLink structs.
     */
    function getPageLinks() {
        # XXXX Important here, access control is missing! --jq
        # call user management using the following object
        #$this->SessionInfo->Lang

        $result = array();
        $page_titles = array();
        $pcount = count($this->__Cache->Pages);
        for ($p = 0; $p < $pcount; $p++) {
            $page = &$this->__Cache->Pages[$p];

            $plink = new dzPageLink();
            $plink->Title = $page->Title;
            $plink->Link = $p;

            $result[] = $plink;
        }; // foreach

        return $result;
    } // getPageLinks


    /** Returns the entity in the given position
     *  @param int $Index The index of the entity to get
     */
    function getEntityByNo($Index) {
        $page = &$this->__Cache->Pages[$this->PageNo];
        $ecount = count($page->Entities);
        if (($Index >= 0) && ($Index < $ecount)) {
            $result = $page->Entities[$Index];
        } else {
            $result = NULL;
        }; // else if

        return $result;
    } // getEntityByNo


    /** Get view information
     *  Each view has some basic information like type. Use this access
     *  function to get the information. See the key definitions in the
     *  beginning of this file.
     *  @param string $Key Key to the information
     *  @return string The view info
     */
    function getViewData($Key) {
        return $this->__ViewData->getItem($Key);
    } // getViewData


    /** Set the view information
     *  Each view has some basic information like type. Use this access
     *  function to set the information. See the key definitions in the
     *  beginning of this file.
     *  @param string $Key Key of the informations
     *  @param string $Value Value to assign to the key
     */
    function setViewData($Key, $Value) {
        $this->__ViewData->setItem($Key, $Value);
    } // setViewData


    /** Get form option value
     *  Each form has bunch of option values that defines the behavior of
     *  of the form in the different situations. Use this function to get
     *  option values of the form.
     *  @param string $Key Key to the option value
     *  @param string $AsType Forces conversion to a datatype: int, bool...
     *  @return soSet Option value(s).
     */
    function getFormOption($Key, $AsType = "any") {
        switch ($Key) {
            case DZFO_LIST_COLS:
                $result = $this->FormOptions->ListCols;
                break;
            case DZ_EVENT_INISECTION:

                break;
            default:
                $result = $this->FormOptions->Other->getItem($Key, $AsType);
        } // switch

        return $result;
    } // getFormOption


    /** Returns form event
     *  @param string $Key Event key
     *  @param string $SubKey Optional subkey
     *  @return soSet||string When SubKey is not present, returns soSet.
     */
    function getFormEvent($Key, $SubKey = NULL) {
        if (isset($this->FormOptions->Events[$Key])) {
            $result = $this->FormOptions->Events[$Key];
            if ($SubKey) {
                $result = $result->getItem($SubKey);
            }; // if
        } else {
            $result = NULL;
        }; // if
        return $result;
    } // getFormEvent


    /** Set the form option value
     *  Each form has bunch of option values that defines the behavior of
     *  of the form in the different situations. Use this function to set
     *  option values of the form.
     *  @param string $Key Key of the option value
     *  @param string Value Value to assign to the key
     */
    function setFormOption($Key, $Value) {
        $this->FormOptions->Other->setItem($Key, $Value);
    } // setFormOption


    /** Get specified column property
     *  Use this function to get a property for the specified column. This
     *  function is used also to get information such as column type.
     *  Column (field) has three types of properties: the database table
     *  structure, control (widget) properties and format (layout) properties.
     *  See dzconst.php for type and key defines.
     *  @param string $ColName Column name in format table.column
     *  @param DZCPT_* $Type Type of the property
     *  @param string $Key Property to get
     *  @return string Value of the requested property or an empty string if
     *       property is not found.
     */
    function getColProperty($ColName, $Type, $Key = "") {
        if ($Type == DZCPT_STRUCT) {
            $result = $this->__Struct->getColProperty($ColName, $Key);
        } else {
            $result = $this->__getColProperty_sub($ColName, $Type, $Key);
        }; // else if DZCPT_STRUCT

        return $result;
    } // getColProperty


    /** Get specified column property
     *  Use this function to get a property for the specified column. This
     *  function is used also to get information such as column type.
     *  Column (field) has three types of properties: the database table
     *  structure, control (widget) properties and format (layout) properties.
     *  See dzconst.php for type and key defines.
     *  @param string $ColName Column name in format table.column
     *  @param string $Type Type of the property
     *  @param string $Key Property to get
     *  @return string Value of the requested property or an empty string if
     *       property is not found.
     */
    function __getColProperty_sub($ColName, $Type, $Key = "") {
        $value = "";
        $page = &$this->__Cache->Pages[$this->PageNo];
        switch ($Type) {
            case DZCPT_CONTROL:
                if (soGetClass($page->Controls) == "soset") {
                    $value = $page->Controls->getItem($ColName);
                    $ini = &new dzIniData($this->__Struct,
                                         "[".$ColName."]\n".$value, "\n");
                    $result = $ini->getDzValue($ColName, $Key);
                }; // if
                break;

            case DZCPT_FORMAT:
                if (soGetClass($page->Formats) == "soset") {
                    $result = $page->Formats->getItem($ColName);
                }; // if
                break;
        } // switch

        return $result;
    } // getColProperty_sub


    /** Set the source type: html, xml...
     *  @param string $$Type Input source type.
     */
    function setType($Type = DZT_TYPE_HTML) {
        $this->setViewData(DZ_VI_SOURCE_TYPE, $SrcType);
    } // setType


    /** Set the line delim string in ini files.
     *  @param string $Delim Line delimiter string.
     */
    function setLineDelim($Delim =  "\n") {
        $this->__LineDelim = $Delim;
    } // setLineDelim


    /**  Initialize settings for a pseudo form
     */
    function __initPseudoForm() {
        $this->setViewData(DZ_VI_TYPE, DZ_VIEW_TYPE_PSEUDO);
        $this->setViewData(DZ_VI_MODE, DZM_PSEUDO);
    } // __initPseudoForm


    /** Reads the source and possibly processes it.
     *  @return int Returns true on success, otherwise false.
     */
    function readForm() {

        /* Try to read the cached form. If not success then read
           the source code */
        if ($this->readCache() < 0) {
            soDebug("dzTemplate->readForm: Cache read failed, no cache file?",
                    SOD_LO);
            $type = $this->getViewData(DZ_VI_SOURCE_TYPE);
            switch ($type) {
                default: // "html"
                    if (!$this->readHtml()) return 0; // <-- EXIT!!
                    $this->translate();
            }; // switch

            $ini_str = $this->__Cache->extractIni(DZ_STRUCT_INISECTION, 1);
            $this->__Struct = new dzStruct();
            $this->__Struct->setup($ini_str);

            /* Try to cache the form into file */
            $this->writeCache();
        }; // if

        if ($this->__Struct->isPseudo()) {
            soDebug("dzTemplate->readForm: This is a pseudo form", SOD_DET);
            $this->__initPseudoForm();
        } // if

        /* Setup list cols */
        $ini_str =
            $this->__Cache->extractIni("form", 1);
        $this->FormOptions->setIniStr($ini_str, $this->__Struct);

        /* Setup interfaces */
        $ini_str =
            $this->__Cache->extractIni(DZ_INTERFACES_INISECTION, 1);
        $this->__Interfaces->setup($ini_str);

        /* rename table aliases in entities */
        $this->__renameAliases();

        /* setup groups for struct columns */
        $this->__setupGroups();

        return true;
    } // readForm


    /** Reads the cached form data from the disk
     *  @return int Returns 1 on success, otherwise a negative number:
     *  -1=general error, -2=source stream not of type file
     */
    function readCache() {
        $src_stream = $this->getViewData(DZ_VI_SOURCE);

        /* Check that the source stream type is file. Only file streams
           are cached. */
        if (soGetStreamType($src_stream) != "file") {
            soDebug("dzTemplate->readCache: source '".$src_stream."' ".
                    "not of type 'file' - exiting", SOD_LO);
            return -2; // <--- EXIT!
        };

        /* Find out the paths and names for source, ini and cache files */
        $source = soExtractVal($src_stream, "://", 2);
        $struct_name = soExtractVal(basename($source), ".", -2);
        $paths = dzGetSourceCachePaths($struct_name,
                                       $this->SessionInfo->Lang);
        soDebug("dzTemplate->readCache: struct '".$paths["struct"]."', ".
                "entity_cache='".$paths["entities"]."'", SOD_DET);

        /* Get the file times for source, ini and cache files */
        if (!file_exists($paths["struct"])) return -1; // <-- EXIT
        if (file_exists($source)) {
            $src_time = filemtime($source);
        } else {
            $src_time = NULL;
        }; // else if
        if (file_exists($paths["entities"])) {
            $cache_time = filemtime($entity_cache);
        } else {
            $cache_time = NULL;
        }; // else if

        /* read in cached file? */
        if ($cache_time && ($cache_time == $src_time)) {
            $this->__Cache = unserialize(soFileToString($paths["entities"]));
            $this->__Struct = unserialize(soFileToString($paths["struct"]));

            /* Check that all pages in cache are up to date, too */
            if ($this->__Cache->checkPageStamps($cache_time)) {
                $result = 1;
            } else {
                soDebug("dzTemplate->readCache: cache too old", SOD_LO);
                $result = -2;
            };
        } else {
            soDebug("dzTemplate->readCache: cache too old", SOD_LO);
            $result = -2;
        } // if $cache_time

        return $result;
    } // readCache


    /** Write the form data to cache file on the disk.
     *  The cache is writen only if the source stream type is file
     *  @return int True(1) on success, otherwise negative number:
     *      -2 source stream not file.
     */
    function writeCache() {
        /* Check that the source stream type is file. Only file streams
           are cached. */
        $source = $this->getViewData(DZ_VI_SOURCE);
        if (soGetStreamType($source) != "file") return -2; // <--- EXIT!

        $source = soExtractVal($source, "://", 2);
        $src_time = filemtime($source);
        $source = basename($source);
        $struct_name = substr($source, 0, soLocateStr($source, ".", -1));
        $this->__Struct->setName($struct_name);
        $paths = dzGetSourceCachePaths($struct_name,
                                       $this->SessionInfo->Lang);

        soDebug("dzTemplate: Caching '".$source."/".$struct_name.
                "' source to '".$paths["entities"].
                "' and '".$paths["struct"]."'", SOD_DET);

        $data = serialize($this->__Cache);
        $file = new soOutputStream("file://".$paths["entities"], $data);
        $ent_res = $file->Error;
        $data = serialize($this->__Struct);
        $file = new soOutputStream("file://".$paths["struct"], $data);
        $ini_res = $file->Error;

        if ($ent_res || $ini_res) $this->Error = DZ_TMPL_CACHE_WRITE_FAILED;

        /* Set the cache file timestamps to src file timestamp */
        @touch($paths["entities"], $src_time);
        @touch($paths["struct"], $src_time);

        return 1;
    } // writeCache()


    /** Translated source template according to user's language.
     */
    function translate() {
        $page_nos = array_keys($this->__Cache->Pages);
        foreach ($page_nos as $page_no) {
            $this->__translate_sub($page_no);
        };
    } // translate


    /** Translated source template according to user's language.
     */
    function __translate_sub($PageNo) {
        /* Sort out the lang file name and input it */
        $lang_stream = $this->getViewData(DZ_VI_SOURCE);
        $dot_pos = soLocateStr($lang_stream, ".", -1);
        $lang_stream = substr($lang_stream, 0, $dot_pos).
                       DZS_LANG_EXT;
        $this->__Lang->readIni($lang_stream);
        $lang = $this->SessionInfo->Lang;

        /* Loop all text cells and translate if possible */
        foreach ($this->__Cache->Pages[$PageNo]->TextCells as $cell_no) {
            $entity = &$this->__Cache->Pages[$PageNo]->Entities[$cell_no];
            $tag = trim(soStripCtrlChars(strip_tags($entity->Value)));
            $result = $this->__Lang->getTranslation($tag, $lang, "label");
            if ($result != "") {
                $entity->Value = $result;
                $this->__Cache->Pages[$PageNo]->Entities[$cell_no] = $entity;
            }; // if

        }; // foreach

        /* Translate and set title, if found */
        $page_title = $this->__Cache->Pages[$PageNo]->Title;
        $title = $this->__Lang->getTranslation($page_title, $lang, "label");

        if (!$title) $title = $page_title;
        $this->__Cache->Pages[$PageNo]->Title = $title;
    } // __translate_sub


    /** Read in cached or source Html file.
     *  @return bool True on success otherwise false
     */
    function readHtml() {
        /* XXX Implement other streamtypes than file */
        $src_stream = $this->getViewData(DZ_VI_SOURCE);
        soDebug("dzTemplate: Reading html form from file '$src_stream'",
                SOD_LO);

        /* Read the form source. First include the parser implementation for
         * html source. Then create the parser for the source */
        include_once("dzinhtml.php");
        $parser = new dzParser($src_stream);
        if ($parser->Error) {
            soDebug("dzTemplate: Reading the source failed.", SOD_E_HI);
            $this->Error = DZ_TMPL_PARSE_FAILED;
            $result = false;
        } else {
            $this->__Cache = $parser->parse();
            if ($parser->Error) {
                soDebug("dzTemplate: Parsing the source failed.", SOD_E_HI);
                $this->Error = DZ_TMPL_PARSE_FAILED;
                $result = false;
            } else {
                soDebug("dzTemplate: Parsing the source ok.", SOD_DET);
                $result = true;
            } // if
        }; // else if

        return $result;
    } // readHtml


    /** Removes white spaces from a string.
     *  Strip tabs and successive spaces from a string. Only one
     *  space is left to separate words. line feeds are replaced with
     *  delimiter given as parameter.
     *  @param string $Source The string to strip
     *  @param string $LineDelim String to separate different lines
     *  @return string The string where extra characters are stripped
     */
    function __stripIniStr($Source, $LineDelim) {
        $Source = soReplaceStr($Source, "\c", "");
        $Source = soReplaceStr($Source, "\n\n", "\n");
        $Source = str_replace("\t", " ", $Source);
        $Source = str_replace("\n", $LineDelim, $Source);

        /* str_replace seems not to work properly. So we must control that
           no successive spaces are found */
        while (soStrCount($Source, "  ")) {
            $Source = str_replace("  ", " ", $Source);
        } // while

        return $Source;
    } // __stripIniStr()


    /** Search for form help
     *  Searches help file for the given form. The base name of the help
     *  file is same as form source file's. But the extension is 'hlp'.
     *  @param string $SrcFile The dataxi source file
     *  @return string Path to help file
     */
    function searchFormHelp($SrcFile) {
        soDebug("dzTemplate: Searching help file. Lang=".
                $this->SessionInfo->Lang, SOD_DET);
        $help_file = soExtractVal(basename($SrcFile), ".", -2);
        $path = dzGetIniPath(DZID_FORM_SOURCE_FOLDER)."/";
        $result = "";
        /* Search for help file in users language */
        soDebug("dzTemplate: Searching help file:".$path.$help_file."_".
                $this->SessionInfo->Lang.".hlp", SOD_DET);
        if (file_exists($path.$help_file."_".$this->SessionInfo->Lang.".hlp")) {
            $result = $help_file."_".$this->SessionInfo->Lang.".hlp";
        } else {
            /* Search for default help file*/
            if (file_exists($path.$help_file.".hlp"))
                $result = $help_file.".hlp";
        } // if
        soDebug("dzTemplate: Help file=$result", SOD_DET);

        return $result;
    } // searchFormHelp


    /** Add init action on the form
     * When form is started at user end the init actions are first
     * executed. output filter uses this to decide what kind of
     * init actions to put assign to form.
     * E.g. Init action might be moving focus to certain widget
     * See different actions in the beginning of the this file.
     * @param int $Action Init action type
     * @param string $Parameter Parameter for the action
     */
    function addInitAction($Action, $Parameter = "") {
        $this->ScriptFeatureSpool->setInitAction($Action, $Parameter);
    } // addInitAction


    /** Fixes alias table name in a entity->Name properties.
     *  Loops through dataxi aware elements in Cache and replaces alias table
     *  names with real names in entity->Name properties. Also, important
     *  feature, adds columns not belonging to any group by this far to a
     *  special main group that holds all columns that are not treated as rows.
     *  @return int No. of renames done.
     */
    function __renameAliases() {
        $p = count($this->__Cache->Pages) -1;
        while ($p >= 0) $this->__renameAliases_sub($p--);
    } // __renameAliases


    /** Actual routine for __renameAliases.
     *  @param int $PageNo Page no. to translate.
     *  @return int No. of renames done.
     */
    function __renameAliases_sub($PageNo) {
        $result = 0;
        $cache = &$this->__Cache;
        $page = &$cache->Pages[$PageNo];

        /* loop through each dataxi aware element */
        foreach ($page->DataCellNos as $ent_no) {
            $entity = &$page->Entities[$ent_no];
            $name = $this->__Struct->findCol($entity->Name);
            if (($name != "") && ($name != $entity->Name)) {
                $entity->Name = $name;
                $result++;
            }; // if $name !=

            /* add a column to groups */
            $group_id = dzGetGroupID($PageNo, $entity->ParentNo);
            $group = $cache->Groups->getItem($group_id);

            /* special main group get/init */
            if ($cache->Groups->Error) {
                $group_id = dzGetGroupID(0, 0);
                $group = $cache->Groups->getItem($group_id);
                if ($cache->Groups->Error) {
                    $group = new dzGroup();
                    $group->Inserting = DZGI_ONE_END;
                    $group->RowLimit = 1;
                    $group->Type = DZGT_MAIN;
                };
            };

            if (!in_array($entity->Name, $group->Cols)) {
                $group->Cols[] = $entity->Name;
                if ($this->__Struct->getColProperty($entity->Name,
                                                    DZCP_REQUIRED)) {
                    $group->Required[] = $entity->Name;
                }; // if required
            };
            $cache->Groups->setItem($group_id, $group);

        }; // foreach

        /** rename controls and formats, too */
        for ($i = 0; $i < 2; $i++) {
            if ($i == 0) {
                $set = $page->Controls;
            } else {
                $set = $page->Formats;
            };

            $keys = $set->getkeys();
            foreach ($keys as $key) {
                $name = $this->__Struct->findCol($key);
                if (($name != "") && ($name != $key)) {
                    $set->renameItem($key, $name);
                    $result++;
                }; // if $name !=
            }; // foreach

            if ($i == 0) {
                $page->Controls = $set;
            } else {
                $page->Formats = $set;
            };
        }; // for $i
        return $result;
    } // __renameAliases_sub


    /** Sets up groups for source entity/struct columns.
     *  Loops all groups, takes the Dataxi column name from the
     *  SourceEntities and stores it to the array of dzGroups to be used
     *  later by dzPurkki and dzAsm.
     */
    function __setupGroups() {
        $cache = &$this->__Cache;
        $group_ids = $cache->Groups->getKeys();
        foreach ($group_ids as $group_id) {
            $grp = $cache->Groups->getItem($group_id);

            $split = dzSplitGroupID($groupno);
            $page_no = $split["page"];
            $group_no = $split["group"];
            $page = &$cache->Pages[$page_no];;

            $parent_entity = $page->Entities[$group_no];
            foreach ($parent_entity->Children as $c_no) {
                $entity = &$page->Entities[$c_no];
                if (!$entity->Name) {
                    soDebug("dztemplate->__setupGroups: ".
                            "null column name encountered at cell ".
                            $c_no, SOD_HI);
                } else {
                    if (!in_array($entity->Name, $grp->Cols)) {
                        $grp->Cols[] = $entity->Name;
                    };
                    $keycol = $this->__Struct->getKeyCol($entity->Name);
                    $auto = (strtolower($keycol->Default) == DZDCV_AUTO);
                    if ($auto &&
                        ($keycol->Type == SODT_INT)) {
                        $grp->AutoNum =
                            $this->__Struct->findCol($entity->Name);
                    };

                }; // else if !name
            }; // foreach children entity

            $cache->Groups->setItem($group_id, $grp);
        }; // foreach
    } // setupGroups


    /** Returns group ID for a column.
     *  @param string $ColName Column name to get a group ID for.
     *  @return int Group ID or NULL for non-existing column.
     */
    function getGroupID($ColName) {
        $cache = &$this->__Cache;
        $group_ids = $cache->Groups->getKeys();
        $result = NULL;

        $icount = count($group_ids);
        $i = $icount -1;
        while (($i >= 0) && ($result == NULL)) {
            $grp = $cache->Groups->getItem($group_ids[$i]);
            if (in_array($ColName, $grp->Cols)) $result = $group_ids[$i];
            $i--;
        }; // while

        return $result;
    } // getGroupID


    /** Get the list of the form's groups
     *  @return array/str The list of group ID:s
     */
    function getGroups() {
        return $this->__Cache->Groups->getKeys();
    } // getGroups


    /** Get the db alias
     *  Return the forms db alias
     *  @return string Db alias name
     */
    function getDbAlias() {
        return $this->__Cache->DbAlias;
    } // getDbAlias


    /** Get the form title (title of the 1st page)
     *  This is something more human that Xyz.47 - 5g
     *  @return string Form title
     */
    function getFormTitle() {
        return $this->__Cache->Pages[0]->Title;
    } // getFormTitle
    
    
    /** Returns current page
     *  @return dzSEPageCache Current page.
     */
    function getPage() {
        return $this->__Cache->Pages[$this->PageNo];
    } // getPage


    /** Returns source cache
     *  @return dzSECache Source cache
     */
    function getCache() {
        return $this->__Cache;
    } // getCache


    /** Returns array of fetching declarations
     *  This is a wrapper for dzStruct class method
     *  @param string $ColName Fetching source col
     *  @return array/dzFetch Array of fetching objects or NULL for bad col
     *      name
     */
    function getFetchingArray($SourceCol) {
        return $this->__Struct->getFetchingArray($SourceCol);
    } // getFetchColNames


    /** Returns the key list fo fetch request
     *  Returns the list of keys or values. This list can be sent to Laituri
     *  as a list of the where keys or values assigned to keys when sending
     *  fetch request.
     *  Note! Value can be a reference to some field. Values from these
     *  referenced fields should be fetched using getFetchWhereValues()
     *  method in dzPurkki.
     *  @param string $WhereCols "+" delimited list of where cols
     *  @param string $Type Thether "key" or "val"
     *  @return array/str Keys
     */
    function getFetchRequestKeys($WhereCols, $Type = "key") {
        $result = array();

        if ($Type == "key") {
            $val_ind = 1;
        } else {
            $val_ind = 2;
        };

        $cols = soExplode($WhereCols, "+");
        $ccount = count($cols);
        /* The value is not given for the last key because it is got from the
           field that is validated */
        if ($Type != "key") $ccount--;
        for ($c = 0; $c <$ccount; $c++) {
            $key = soExtractVal($cols[$c], "/", $val_ind);
            $result[] = $key;
        } // for

        return $result;
    } // getFetchRequestKeys


    function isPseudo() {
        return $this->__Struct->isPseudo();
    } // isPseudo


} // dzTemplate

?>
